A virtual machine is software that acts as an interface between a computer program that has been compiled into instructions understood by the virtual machine and a microprocessor (or “hardware platform”) that actually performs the program's instructions. Once a virtual machine has been provided for a platform, any program compiled for that virtual machine can run on that platform.
One popular virtual machine is known as the Java virtual machine (VM). The Java virtual machine specification defines an abstract rather than a real “machine” (or processor) and specifies an instruction set, a set of registers, a stack, a “garbage-collected heap,” and a method area. The real implementation of this abstract or logically defined processor can be in other code that is recognized by the real processor or be built into the microchip processor itself.
The output of “compiling” a Java source program (a set of Java language statements) is called bytecode. A Java virtual machine can either interpret the bytecode one instruction at a time (mapping it to a real microprocessor instruction) or the bytecode can be compiled further for the real microprocessor using what is called a just-in-time (JIT) compiler.
The Java programming language supports multi-threading, and therefore Java virtual machines must incorporate multi-threading capabilities. Multi-threaded computing environments allow different parts of a program, known as threads, to execute simultaneously. In recent years, multithreaded computing environments have become more popular because of the favorable performance characteristics provided by multithreaded applications.
Compared to the execution of processes in a multiprocessing environment, the execution of threads may be started and stopped very quickly because there is less runtime state to save and restore. The ability to quickly switch between threads can provide a relatively high level of data concurrency. In the context of a multi-threaded environment, data concurrency refers to the ability for multiple threads to concurrently access the same data. When the multi-threaded environment is a multi-processor system, each thread may be executed on a separate processor, thus allowing multiple threads to access shared data simultaneously.
Java is gaining acceptance as a language for enterprise computing. In an enterprise environment, the Java programs may run as part of a large-scale server to which many users have concurrent access. A Java virtual machine with multi-threading capabilities may spawn or destroy threads as necessary to handle the current workload. For example, a multi-threading Java virtual machine may be executing a first Java program in a first Java thread. While the first Java program is executing, the server may receive a request to execute a second Java program. Under these circumstances, the server may respond to the request by causing the Java virtual machine to spawn a second Java thread for executing the second Java program.
While the Java virtual machine is multi-threaded, the server in which the virtual machine is embedded may be executing in a single-threaded environment. Consequently, the threads used by the Java VM are “virtual”, and must be executed one-at-a-time in the single “native” thread of the environment. The nature of the native thread will vary from platform to platform. For example, Windows NT has built-in support for “threads”. Consequently, in a Windows NT environment the native thread may simply be an operating system thread provides by Windows NT. In a Solaris environment, on the other hand, memory may be allocated for a native thread stack, and the processor may be instructed to transfer execution into the stack thus created. The present invention is not limited to any particular environment, nor any particular native thread or stack implementation.
When a multi-threaded Java virtual machine is embedded in server running in a single threaded environment, the native thread in which the server is running is referred to as the master native thread. The Java VM initially creates a Java virtual thread that executes in the master native thread, and therefore uses the native call stack of the master native thread (the “master native stack”). The Java VM thread that executes in the master native thread is referred to herein as the master VM thread. Once the master VM thread is initiated, the Java VM may spawn additional VM threads to execute requested tasks. Each of the non-master VM threads thus spawned is associated with its own native thread, and therefore has its own native stack.
Each VM thread is capable of invoking functions in the server in which the virtual machine is embedded. For example, if the server in which the Java VM is embedded is a database server, then the Java programs that are running in the threads of the Java VM may make calls out to the database server to issue a database query, to retrieve values from memory, etc.
In addition to VM threads making calls to routines within the server, routines within the server may make calls to the Java VM. While such calls are received by the master VM thread, the operations performed by the virtual machine in response to those calls may actually be executed by VM threads other than the master VM thread. The calls made from the server routines to the virtual machine may, in fact, be made by server routines that have been invoked by call outs made by VM threads. Thus, any number of recursive calls may exist, from the VM threads out to the server, and from the server back to the VM.
Single-threaded servers that incorporate multi-threaded virtual machines raise a variety of technical challenges. For example, the server may place an error handling frame on the master native stack prior to making a call to the VM. The master VM thread may spawn another VM thread to service the call. The non-master VM thread thus spawned, which has its own non-master native thread, may call back to a particular routine within the server. If an error occurs within the routine, the exception raised by the error may trigger an error handling routine that attempts to “unwind” the stack of the non-master native thread in which the error occurred. The unwinding operation searches for the error-handling frame that was placed on the master native stack prior to the invocation of the routine that caused the exception. Unfortunately, the error-handling frame is in the master native stack, not in the non-master native stack associated with the VM thread in which the exception was raised. Consequently, an attempt to unwind the stack will unwind the entire non-master native stack of the non-master VM thread without encountering the error-handling frame.
Based on the foregoing, it is clearly desirable to provide improved techniques for handling call outs from a multi-threaded virtual machine to routines in a server running in a single-threaded environment.